import 'dart:convert';
import 'dart:isolate';

import 'package:logger/logger.dart';

import '../dao/ImFileDao.dart';
import '../dao/ImMessageDao.dart';
import '../dao/ImSessionDao.dart';
import '../pojo/ImPojo.dart';
import '../service/ImMessageReadService.dart';
import '../socket/dto/base/Resp.dart';
import '../util/DateUtil.dart';
import '../util/FileUtil.dart';
import '../util/ImConstant.dart';
import '../util/ImSingleton.dart';
import '../util/ImageUtil.dart';
import '../util/MessageUtil.dart';
import '../util/Objects.dart';
import '../util/UiUtil.dart';
import 'FileDownloadSocket.dart';
import 'ImSocket.dart';
import 'UploadAndDownloadDto.dart';

const fileDownloadPSize = ImConstant.FILE_DOWLOAD_P_SIZE;
const String TAG = 'FileDownloadService';

class FileDownloadService {

  static final _logger = Logger(printer: PrettyPrinter(),);

  static Future<void> downFile(ImMessage message, DownloadCallback callback, String downServerIp) async {
    message.initMessageExt();

    if (message.messageExt == null) {
      _logger.w("message ext is null, ${message.clientId}", time: DateTime.now());
      return;
    }
    var fileExistedType = await _checkFileDownloaded(message);
    var isSuccess = await _handleExistedDownloaded(message, fileExistedType);
    if (isSuccess) {
      callback(FileDownloadResult(message.messageExt!.size??0, message.messageExt!.size??0, true, ''));

      var imMessageDao = ImMessageDao();
      await imMessageDao.changeMessageFileStatus(message.id!, ImMessageFileStatus.downloaded);

      _updateUi(message);
      return;
    }

    message.fileStatus = ImMessageFileStatus.downloading;
    UiUtil.uiUpdateMessage(message);

    // 开启下载线程
    FileDownloadWorker downloadWork = FileDownloadWorker();
    try {
      await downloadWork.downFile(message, _wrapDownloadCallback(downloadWork, callback, message), downServerIp);
    } catch(error, stackTrace) {
      _logger.e('Download error', error: error, stackTrace: stackTrace);
      message.fileStatus = ImMessageFileStatus.download_fail;
      UiUtil.uiUpdateMessage(message);
      downloadWork.close();
    }
  }

  /// 检查文件是否已经下载
  static Future<FileExistedType> _checkFileDownloaded(ImMessage message) async {
    if (message.fileStatus == ImMessageFileStatus.downloaded) return FileExistedType.downloaded;
    // 检查ImFile中是否已经有这个文件
    var fileDao = ImFileDao();
    var dbFile = await fileDao.getByMd5AndUserId(message.messageExt!.md5!, message.currentUserId);
    if (dbFile != null) {
      var filePath = (await FileUtil.getFileRoot()) + dbFile.path;
      var isExisted = FileUtil.fileExisted(filePath);
      if (isExisted) return FileExistedType.downloadInUserDir;
    }

    var filePath = await MessageUtil.getFilePath(message);
    var isExisted = FileUtil.fileExisted(filePath);
    if (isExisted) return FileExistedType.downloaded;

    var rootDir = await FileUtil.getFileRoot();
    var day = DateUtil.formatDate2(message.createTime, "yyyyMMdd");
    var localDir = "/${message.currentUserId}/download/$day/";
    var cacheDir = "/${message.currentUserId}/cache/$day/";

    // 检查用户下载目录中是否已经下载过这个文件
    filePath = rootDir + localDir + message.messageExt!.originalName;
    isExisted = FileUtil.fileExisted(filePath);
    if (isExisted) return FileExistedType.downloadInUserDir;

    // 检查缓存文件中是否已经下载文件
    filePath = rootDir + cacheDir + message.messageExt!.originalName;
    isExisted = FileUtil.fileExisted(filePath);
    if (isExisted) return FileExistedType.downloadInCache;

    return FileExistedType.nodownload;
  }

  static Future<bool> _handleExistedDownloaded(ImMessage message, FileExistedType fileExistedType) async {
    if (fileExistedType == FileExistedType.downloadInCache) { // 下载文件在下载缓存目录中
      // 将文件下载缓存目录移动到用户目录
      await _moveCacheFileToUserDir(message);
      // 更新本地文件数据库
      await _updateMessageFileInfo(message);

      return true;
    } else if (fileExistedType == FileExistedType.downloadInUserDir) { // 下载文件在用户目录中
      // 更新本地文件数据库
      await _updateMessageFileInfo(message);

      return true;
    } else if (fileExistedType == FileExistedType.downloaded) { // 文件已经下载成功
      return true;
    }
    return false;
  }

  // 将下载文件从缓存文件目录中移动用户目录文件中
  static Future<void> _moveCacheFileToUserDir(ImMessage message) async {
    var messageExt = message.messageExt!;

    var rootDir = await FileUtil.getFileRoot();
    var day = DateUtil.formatDate2(message.createTime, "yyyyMMdd");
    var cacheDir = "/${message.currentUserId}/cache/$day/";
    var userDir = "/${message.currentUserId}/download/$day/";

    var cacheFile = rootDir + cacheDir + messageExt.originalName;
    var destFile = rootDir + userDir + messageExt.originalName;
    await FileUtil.moveFile(cacheFile, destFile);
    messageExt.fileUrl = userDir;
    message.content = destFile;
  }

  static Future<void> _updateMessageFileInfo(ImMessage message) async {
    var day = DateUtil.formatDate2(message.createTime, "yyyyMMdd");

    var rootDir = await FileUtil.getFileRoot();
    var userDir = "/${message.currentUserId}/download/$day/";

    // 检查用户下载目录中是否已经下载过这个文件
    // var filePath = rootDir + userDir + message.messageExt!.originalName;
    message.content = userDir + message.messageExt!.originalName;
    message.messageExt!.fileUrl = userDir;
    message.fileStatus = ImMessageFileStatus.downloaded;
    message.messageExt!.fileUrl = userDir;
    message.updateTime = DateTime.now();

    message.ext = jsonEncode(message.messageExt!.toJson());
    message.updateTime = DateTime.now();

    var messageDao = ImMessageDao();
    await messageDao.batchUpdates(message.currentUserId, [message]);
  }

  static DownloadCallback _wrapDownloadCallback(FileDownloadWorker downloadWork, DownloadCallback callback, ImMessage
    message) {
    return (FileDownloadResult result) {
      _logger.i('$TAG, [Download call back], ${result.fileDownloadSize}/${result.fileSize}/${result.errorMsg}');
      if (result.isSuccess) {
        _downloadSuccess(message);
        downloadWork.close();
      }
      callback(result);
    };
  }

  static void _downloadSuccess(ImMessage message) async {
    _logger.i("$TAG, [Download success]");
    await _handleExistedDownloaded(message, FileExistedType.downloadInUserDir);

    var imMessageDao = ImMessageDao();
    await imMessageDao.changeMessageFileStatus(message.id!, ImMessageFileStatus.downloaded);

    // 计算图片尺寸
    if (message.messageType == ImMessageType.Image) {
      if (Objects.isNotNull(message.messageExt) && (Objects.isNull(message.messageExt?.imageWidth) || Objects.isNull(message.messageExt?.imageHeight))) {
        var path = await MessageUtil.getFilePath(message);
        var imageSize = await ImageUtil.getImageSize(path);
        message.messageExt!.imageWidth = imageSize[0];
        message.messageExt!.imageHeight = imageSize[1];
        message.init();

        await imMessageDao.batchUpdateExts(message.currentUserId, [message]);
      }
    }

    await saveFileToDb(message);

    _updateUi(message);
  }

  static void _updateUi(ImMessage message) async {
    message.fileStatus = ImMessageFileStatus.downloaded;
    message.updateTime = DateTime.now();

    UiUtil.uiUpdateMessage(message);

    // 消息已读
    if (ImSingleton.getInstance().messageListCubit != null) {
      if (ImSingleton.getInstance().messageListCubit!.canShowMessage(message)) {
        var messageReadService = ImMessageReadService.getInstance();
        messageReadService.asyHandleReadMessage(message.sessionId!, message.currentUserId, [message]);
      }
    }

    _logger.i("$TAG, [Download success], 文件下载成功");
  }
}

// 下载线程
class FileDownloadWorker {

  final _logger = Logger(printer: PrettyPrinter(),);

  late Isolate isolate;
  late SendPort isolateSendPort;

  FileDownloadWorker() {}

  downFile(ImMessage message, DownloadCallback callback, String downServerIp) async {
    _logger.i('Download downFile started');

    ReceivePort receivePort = ReceivePort();
    isolate = await Isolate.spawn(_doDownloadWorker, receivePort.sendPort);
    _logger.i('Download worker created, ${isolate.debugName}');

    receivePort.listen((dynamic data){
      _logger.i('Download receive data from worker');
      if (data is FileDownloadResult) {
        FileDownloadResult result = data;
        callback(result);
        if (result.isSuccess) {

          close();
        }
      } else if (data is SendPort) {
        isolateSendPort = data as SendPort;

        _sendDownloadDataToWorker(downServerIp, message);
      } else{
        _logger.e('Download worker receive data');
      };
    });
  }

  // 发送数据至DownloadWorker线程
  void _sendDownloadDataToWorker(String ip, ImMessage message) async {
    try {
      var originName = message.messageExt?.originalName??message.content;

      var tenantCode = ImConstant.TENANT_CODE;

      var rootDir = await FileUtil.getFileRoot();
      var day = DateUtil.formatDate2(message.createTime, "yyyyMMdd");
      var cacheDir = "$rootDir/${message.currentUserId}/cache/$day/";
      FileUtil.mkDirs(cacheDir);

      var userFileDir = "$rootDir/${message.currentUserId}/download/$day/";
      FileUtil.mkDirs(userFileDir);

      var data = FileDownloadData(tenantCode!, message.currentUserId, message.id!, message.serverId!,
          originName!, message.messageExt!.size!, message.messageExt!.md5!, message.messageExt?.fileUrl??'',
          cacheDir, userFileDir, ip, 9190);

      isolateSendPort.send(data);
      _logger.i('Download send file info to worker');
    } catch (error, stackTrace) {
      _logger.e('Download send file info to worker error', error: error, stackTrace: stackTrace);
    }
  }

  void close() {
    _logger.i('Download worker close!');
    isolate?.kill();
  }
}

final _logger = Logger(printer: PrettyPrinter(),);

void _doDownloadWorker(SendPort sendPort) {
  _logger.i("DownloadWorker start!!!!!!!");
  ReceivePort receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);

  receivePort.listen((dynamic message) async {
    _logger.i("DownloadWorker receive request:  ${message}");

    var fileDownloadData = message as FileDownloadData;
    var fileDownloadSocket = FileDownloadSocket(fileDownloadData.tenantCode, fileDownloadData.userId,
        fileDownloadData.messageId, fileDownloadData.messageServerId, fileDownloadData.fileOriginName,
        fileDownloadData.fileSize, fileDownloadData.md5, fileDownloadData.fileUrl, fileDownloadData.cacheDir,
        fileDownloadData.userDir, fileDownloadData.ip, fileDownloadData.port);

    fileDownloadSocket.setDownloadListener((FileDownloadResult fileDownloadResult)=>{
      fileDownloadResultCallBack(sendPort, fileDownloadSocket, fileDownloadResult)
    });

    try {
      await fileDownloadSocket.connect();
      var response = await fileDownloadSocket.sendFileDownloadReq(60);
      _logger.i("$TAG, [DownloadWorker send file req result]");
      if(response is ExceptionResp) {
        sendPort.send(FileDownloadResult(0, 0, false, (response as ExceptionResp).errorMsg??''));
        return;
      }
      if(response is OkResp) {
        _logger.i("$TAG, [DownloadWorker send file req result success], ${jsonEncode(response)}");
      }
    } catch (error, stackTrace) {
      _logger.e("$TAG [DownloadWorker error]", error: error, stackTrace: stackTrace);
      sendPort.send(FileDownloadResult(0, 0, false, error.toString()));
      closeSocket(fileDownloadSocket);
    }
  });
}

void fileDownloadResultCallBack(SendPort sendPort, ImSocket imSocket, FileDownloadResult fileDownloadResult) async {
  sendPort.send(fileDownloadResult);
  if (fileDownloadResult.errorMsg != null && fileDownloadResult.errorMsg != '') { // 出现异常
    await closeSocket(imSocket);
  } else if (fileDownloadResult.isSuccess) { // 文件下载成功
    await closeSocket(imSocket);
  }
}

Future<void> closeSocket(ImSocket imSocket) async {
  await imSocket.close();
  _logger.i("$TAG, [Download socket close]");
}

saveFileToDb(ImMessage message) async {
  if (!(message.messageType! == ImMessageType.File
      || message.messageType! == ImMessageType.Audio)) return;

  var fileDao = ImFileDao();

  try {
    var messageExt = message.messageExt!;
    var sessionId = message.imSession?.serverId;

    var dbFile = await fileDao.getByMd5AndUserId(messageExt.md5!, message.currentUserId);
    if (dbFile != null) return;

    if (Objects.isNull(sessionId)) {
      var sessionDao = ImSessionDao();
      var session = await sessionDao.getById(message.sessionId!);
      sessionId = session?.serverId!;
    }
    var imFile = ImFile(
      currentUserId: message.currentUserId,
      sessionId: sessionId!,
      messageId: message.serverId!,
      fileName: messageExt.originalName!,
      md5: messageExt.md5!,
      fileSize: messageExt.size!,
      path: message.content!,
      isRelativePath: messageExt.remote!,
    );

    await fileDao.batchInserts([imFile]);
  } catch (error, stackTrace) {
    _logger.e('[saveImFile error]', error: error, stackTrace: stackTrace);
  }
}